<?php
/**
 * Nimo: 模板引擎
 * 
 * @author andery <andery@foxmail.com>
 * @link http://www.minophp.com/
 * @version $Id: CTemplate.php 2010-05-19 11:21:19Z andery $
 */
class CTemplateNimo{
    // 模板缓存时间 为0表示关闭模板缓存
    public  $cache = 0;
    // 模版文件目录
    public  $tpldir = 'template/default/';
    // 缓存文件目录
    public  $objdir = 'data/temp/compile/';
    // 模板标签起始符
    public $left_delimiter='{';
    // 模板标签结束符
    public $right_deliniter='}';
    // 文本处理
    private $_folded_blocks = array();
    // 输出数据
    private $_data = array();
    
    /**
     * 构造函数
     * @param array $tpl_config 导入配置文件
     */
    public function __construct($tpl_config)
    {
        if (is_array($tpl_config)) {
            foreach($tpl_config as $key=>$value){
                if(empty($value) || !property_exists($this, $key)){
                    continue;
                }else{
                    $this->$key = $value;
                }
            }  
        }
        
    }
    
    /**
     * 模板数据赋值
     */
    public function __set($name, $value)
    {
    	$this->_data[$name] = $value;
    }
    
    /**
     * 模板输出变量赋值
     * @param mixed $tpl_data 变量数据或者变量名
     * @param mixed $value 变量值
     */
    public function assign($tpl_data, $value = '')
    {
        if (!is_array($tpl_data) && $tpl_data != '') {
            $this->_data[$tpl_data] = $value;
        } else {
            foreach ($tpl_data as $k=>$v) {
                $this->_data[$k] = $v;
            }
        }
    }
    
    /**
     * 模板输出函数
     * @param string $tplfile 模板文件名
     * @param array $tpl_data 输出的数据数组
     */
    public function display($tplfile)
    {
        //需要输出的变量转换
        if ($this->_data!=array()) extract($this->_data);
        //包含模板缓存文件
        $tplfile = $this->tpldir.$tplfile;
        if (empty($this->_data['tpl_dir'])) {
            $this->_data['tpl_dir'] = $this->tpldir;
        }
        include_once $this->_loadTemplate($tplfile);
    }
    
    /**
     * 检测模版文件是否存在
     * 此方法可以检测多种格式的模板文件，类型不同的相同模板名取第一个
     * @param string $tplfile 模版文件名
     * @return mixed 不存在返回false 存在返回模版文件名
     */
    private function _tplExists($tplfile){
        if (file_exists($tplfile)) {
            return true;
        } else {
            return false;
        }
    }
    
    /**
     * 加载模板文件并生成缓存文件
     * @param  string $tplfile 模板文件名
     * @return string 模板缓存文件名
     */
    private function _loadTemplate($tplfile)
    {
        $is_exists = $this->_tplExists($tplfile);
        if (!$is_exists) {
            throw new CException("Template file : $tplfile Not found or have no access!");
        } else {
            return $this->_cacheTemplate($tplfile);
        }
    }
    
    /**
     * 检查缓存文件是否可用
     * @param  string $tplfile 模板文件名
     * @return string 模板缓存文件名
     */
    private function _cacheTemplate($tplfile)
    {
        /**
         * 以下情况需要重新编译模板文件
         * 1.缓存文件不存在
         * 2.如果模板文件在 生成缓存文件之后有修改过
         * 3.没有开启模板缓存功能
         * 4.缓存文件已经过期
         */
        $cache_file = $this->objdir.md5($tplfile).'.php';
        if ((!is_file($cache_file))||(filemtime($cache_file) > filemtime($cache_file))||($this->cache==0||time() > filemtime($cache_file)+$this->cache)) {
            $this->parseTemplate($tplfile, $cache_file);
        }
        return $cache_file;
    }
    
    /**
     * 编译模板文件
     * @param string $tplfile 模板文件名
     * @param string $cache_file 模板缓存文件名
     */
    private function parseTemplate($tplfile, $cache_file)
    {
        //读取模版内容 解析模板标签
        $source = $this->_parseContent($tplfile);
        
        //解析完成 写入到缓存文件
        if(@$fp = fopen($cache_file, 'w')) {
            flock($fp, 2);
            fwrite($fp, stripslashes($source));
            fclose($fp);
        } else {
            trigger_error("File: $cache_file can not be write!");
        }
    }
    
    /**
     * 模板内容处理
     */
    private function _parseContent($tplfile)
    {
        //读取模版内容 解析模板标签
        $source = file_get_contents($tplfile);
        //忽略literal中的内容
        preg_match_all("/\{literal\}(.*?)\{\/literal\}/eis", $source, $match,  PREG_SET_ORDER);
        $this->_folded_blocks = $match;
        reset($this->_folded_blocks);
        
        $source = preg_replace("/\{literal\}(.*?)\{\/literal\}/eis", "'{block}'", $source);
        
        $source = preg_replace("/" . $this->left_delimiter . "([^\}\{\n]*)" . $this->right_deliniter . "/e", "\$this->_parseTag('\\1');", $source);
        
        $source = preg_replace("/\{block\}/eis", "\$this->_parseBlockTag();", $source);
        return $source;
    }
    
    /**
     * 解析模板标签
     * @param string $tag 标签内容
     * @return string PHP代码
     */
    private function _parseTag($tag)
    {
    	$tag= str_replace('/\s+/', ' ', $tag);
    	if ( $tag=='' ) {
    		return '{}';
    	} elseif ($tag{0} == '#' && substr($tag, -1) == '#') {
            //模板注释部分
            return '';
        } elseif ($tag{0} == '$') {
            //变量处理
            return '<?php echo ' . $this->_getVal($tag) . '; ?>';
        } elseif ($tag{0} == '/') {
            //反标签
            $end_tag = substr($tag, 1);
            if ($end_tag == 'literal') {
                return '';
            } elseif ($end_tag == 'if'||$end_tag == 'loop') {
                return '<?php } ; ?>';
            } else {
                return $this->left_delimiter . $tag . $this->right_deliniter;
            }
        } else {
            $tag_temp = explode(' ', $tag);
            $tag_type = array_shift($tag_temp);
            $tag_args = implode(' ', $tag_temp);
            switch ($tag_type) {
                case 'literal':
                    return '';
                    break;
                case 'if':
                    return '<?php if(' . $tag_args . ') { ?>';
                    break;
                case 'else':
                    return '<?php } else { ?>';
                    break;
                case 'elseif':
                    return '<?php } elseif(' . $tag_args . ') { ?>';
                    break;
                case 'loop': //foreach循环
                    return $this->_parseLoopTag($tag_args);
                    break;
                case 'html_options': //下拉选项
                    //待添加
                    break;
                case 'template': //包含模板
                    return '<?php echo $this->display("' . $tag_args . '"); ?>';
                    break;
                case 'file': //导入文件 
                    return $this->_importFile($tag_args);
                    break;
                case 'php': //导入PHP文件
                    return $this->_importFile($tag_args, 'php');
                    break;
                case 'script': //导入js文件
                    return $this->_importFile($tag_args, 'js');
                    break;
                case 'style': //导入css文件
                    return $this->_importFile($tag_args, 'css');
                    break;
                case 'eval': //PHP代码
                    return $this->_parseEvalTag($tag_args);
                    break;
                case 'sprintf':
                    return $this->_sprintf($tag_args);
                    break;
                case 'date':
                    return $this->_date($tag_args);
                    break;
                default :
                    return $this->left_delimiter . $tag . $this->right_deliniter;
                    break;
            }
        }
    }
    
    /**
     * 此标签做文本处理
     */       
    private function _parseBlockTag()
    {
        $block = array_shift($this->_folded_blocks);
        return $block[1];
    }
    
    /**
     * 解析sprinf标签
     *
     * @param  array $arr
     * @return string
     */
    private function _sprintf($tag_args)
    {
        $args_arr = explode(' ', $tag_args);
        foreach ($args_arr as $val) {
            $args[] = $this->_getVal($val);
        }
        $args_str = implode(',', $args);
        return '<?php echo sprintf(' . $args_str . '); ?>';
    }
    
    private function _date($tag_args)
    {
        $args_arr = explode('to', $tag_args);
        $args_arr = array_map('trim', $args_arr);
        $args_arr[0] = $this->_getVal($args_arr[0]);
        return "<?php echo date('".$args_arr[1]."', ".$args_arr[0].")?>";
    }
    
    /**
     * PHP代码
     */
    private function _parseEvalTag($tag_args)
    {
        return "<?php $tag_args?>";
    }
    
    private function _getVal($val)
    {
        if (strrpos($val, '|')) {
            $moddb = explode('|', $val);
            $val = array_shift($moddb);
        }
        if (empty($val)) {
            return '';
        }
        if (strrpos($val, '.')) {
            $valarr = explode('.', $val);
            $val = array_shift($valarr);
            foreach ($valarr as $value) {
                $val .= '[\'' . $value . '\']';
            }
        }
        if (!empty($moddb)) {
            foreach ($moddb as $key => $mod) {
                $s = explode(':', $mod);
                switch ($s[0]) {
                    case 'truncate':
                        $val = 'substr(' . $val . ", 0, $s[1])";
                        break;
                    default :
                        break;
                }
            }
        }
        return $val;
    }
    
    /**
     * 处理模板中循环语句
     * @param string $tag_args
     * @return string //处理之后的PHP代码
     */
    private function _parseLoopTag($tag_args)
    {
        $arr = $key = $val = '';
    	$args_arr = explode(' ', $tag_args);
    	if (count($args_arr)==3) {
    	    $arr = $args_arr[0];
    	    $key = $args_arr[1];
    	    $val = $args_arr[2];
    	} elseif (count($args_arr)==2) {
    	    $arr = $args_arr[0];
    	    $val = $args_arr[1];
    	} else {
    	    return;
    	}
    	$arr = $this->_getVal($arr);
        return $key ? "<?php foreach((array)$arr as $key => $val) {?>" : "<?php foreach((array)$arr as $val) {?>";
    }
    
    /**
     * 导入文件内容写入到模板文件
     * @param stirng $file 文件名
     * @param string $type 文件类型
     */
    private function _getPutin($file, $type = '')
    {
        $contents = file_get_contents($file);
        switch ($type) {
            case 'js':
                $putStr  = "<script type=\"text/javascript\">\r\n";
                $putStr .= $contents . "\r\n";
                $putStr .= "</script>\r\n";
                break;
            case 'css':
                $putStr  = "<style type=\"text/css\">\r\n";
                $putStr .= $contents . "\r\n";
                $putStr .= "</style>\r\n";
                break;
            case 'php':
                $putStr = "<?php ".$contents."; ?>";
                break;
            default:
                $putStr = $contents;
                break;
        }
        return $putStr;
    }
    
    /**
     * 模板导入文件
     * @param string $file 导入文件名
     * @param string $type 导入文件类型 php,js,css
     */
    private function _importFile($tag_args, $type='')
    {
        $isres = $isputin = false;
        $parseStr = '';
        $tag_temp = explode(' ', $tag_args);
        
        //区分站点根目录(false)和模板目录， 并且判断是否将文件内容写入到模板
        if ($tag_temp[0] == 'res') {
            $isres = true;
            if (isset($tag_temp[1]) && $tag_temp[1] == 'putin') {
                $isputin = true;
                $fileStr = str_replace('res putin ', '', $tag_args);
            } else {
                $fileStr = str_replace('res ', '', $tag_args);
            }
        } elseif ($tag_temp[0] == 'putin') {
            $isputin = true;
            if (isset($tag_temp[1]) && $tag_temp[1] == 'res') {
                $isres = true;
                $fileStr = str_replace('putin res ', '', $tag_args);
            } else {
                $fileStr = str_replace('putin ', '', $tag_args);
            }
        } else {
            $fileStr = $tag_args;
        }
        
        $path = !$isres ? '' : $this->tpldir;
        $fileArr =  explode(',', $fileStr);
        foreach ($fileArr as $file){
            if ($type=='') $type = strtolower(substr(strrchr($file, '.'),1));
            $file = $path.$file;
            //文件不存在则抛出错误
            if(!is_file($file)) {
                trigger_error("Template file : $file Not found or have no access!");
            }
            if ($isputin) {
                $parseStr .= $this->_getPutin($file, $type);
            } else {
                switch ($type) {
                    case 'js':
                        $parseStr .= '<script type="text/javascript" src="'.Nimo::app()->getAppUrl().'/'.$file.'"></script>';
                        break;
                    case 'css':
                        $parseStr .= '<link rel="stylesheet" type="text/css" href="'.Nimo::app()->getAppUrl().'/'.$file.'" />';
                        break;
                    case 'php':
                        $parseStr .= '<?php include_once("'.$file.'"); ?>';
                        break;
                    default:
                        //文件类型错误
                        trigger_error("Template file type invalid");
                        break;
                }
            }
        }
        return $parseStr;
    }
}